﻿namespace Grabbers.Realty.FnUa
{
    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Net;
    using System.Text.RegularExpressions;

    using Grabbers.Realty.Domain;
    using Grabbers.Realty.FnUa.Exceptions;

    using HtmlAgilityPack;

    using Lib.Data.Grabbing.DataGrgabbing;
    using Lib.RegularExpressions;

    using Newtonsoft.Json;
    #endregion

    public class FnUaDataGrabber : HtmlDataGrabber<FnUaDataGrabber.AppartmentForSaleAd>
    {
        #region Constants
        private const string _START_QUERY_STRING = "http://fn.ua/listing.php?parent_id[0]=1&parent_id[1]=7&parent_id[2]=129&parent_id[3]=155&pricemin=30000&pricemax=100000&pricecur=2&rooms=3";
        #endregion

        #region Methods (private)
        private AppartmentForSaleAd GrabAdvertisement(HtmlDocument htmlDocument)
        {
            AppartmentForSaleAd result = new AppartmentForSaleAd();

            result.Title = htmlDocument.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[1]/table[1]/tr[1]/td[2]/h1[1]").InnerText;
            string advertisementText = htmlDocument.DocumentNode.SelectSingleNode("/html[1]/body[1]/div[1]/table[1]/tr[1]/td[2]/p[1]").InnerText;

            result.OriginalId = this.ParseId(htmlDocument);
            result.AppartmentArea = this.ParseArea(advertisementText);
            result.AnnouncementDate = this.ParseAdPubDate(htmlDocument);
            result.ContactInfo = this.ParseContacts(htmlDocument);
            result.Price = this.ParsePrice(htmlDocument);
            result.OriginalText = this.ParseOriginalText(htmlDocument);
            result.Images = this.ParseImages(htmlDocument);
            //result.RoomsCount = this.ParseRoomsCount(htmlDocument);

            return result;
        }

        private Image[] ParseImages(HtmlDocument htmlDocument)
        {
            HtmlNodeCollection nodes = htmlDocument.DocumentNode.SelectNodes("//*[@class='highslide']");
            Image[] result = new Image[nodes.Count];
            for (int i = 0; i < result.Length; i++)
            {
                string imgUrl = "http://fn.ua" + nodes[i].Attributes["href"].Value.Substring(1);
                using (WebClient webClient = new WebClient())
                {
                    byte[] imageData = webClient.DownloadData(imgUrl);
                    result[i] = new Image { OriginalUrl = imgUrl, Data = imageData };
                }
            }
            return result;
        }

        private string ParseOriginalText(HtmlDocument htmlDocument)
        {
            HtmlNode adDescription = htmlDocument.DocumentNode.SelectSingleNode("//*[@class='ad-pub-date']");
            return adDescription.InnerText;
        }

        private DateTime? ParseAdPubDate(HtmlDocument htmlDocument)
        {
            string text = htmlDocument.DocumentNode.SelectSingleNode("//*[@class='ad-pub-date']").InnerHtml;
            Match match = Regex.Match(text, @"\d{2}.\d{2}.\d{4} - \d{2}:\d{2}");
            if (!match.Success)
            {
                throw new CanNotDefineAdvertisementDateException();
            }

            string[] dateTimeParts = match.Value.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);

            DateTime result = DateTime.Parse(dateTimeParts[0].Trim() + " " + dateTimeParts[1].Trim());
            return result;
        }

        private AppartmentArea ParseArea(string text)
        {
            Match match = Regex.Match(text, @"(\d+)/(\d+)/(\d+)");
            if (match.Success)
            {
                MatchCollection matchCollection = Regex.Matches(match.Value, RegExpPatterns._NUMBER);
                if (matchCollection.Count == 3)
                {
                    return new AppartmentArea { GrossArea = int.Parse(matchCollection[0].Value), LivingSpace = int.Parse(matchCollection[1].Value), KitchenArea = int.Parse(matchCollection[2].Value) };
                }
            }

            return null;
        }

        private ContactInfo ParseContacts(HtmlDocument htmlDocument)
        {
            HtmlDocument document =
                HtmlDataGrabberTools.GetDocument(string.Format("http://fn.ua/fn_rubrics_menu/backendTest.php?serverData={0}&hashphone=hash&serviceName=showphone", this.ParseId(htmlDocument)));
            Phones phones = JsonConvert.DeserializeObject<Phones>(document.DocumentNode.InnerText);

            ContactInfo result = new ContactInfo();
            result.Phones = new[] { phones.items.aphone1, phones.items.aphone2, phones.items.aphone3 };

            Match match = Regex.Match(htmlDocument.DocumentNode.InnerHtml, "Контакт:( )+<strong>.+</strong>");
            if (!match.Success)
            {
                throw new CanNotDefineContactNameException();
            }

            HtmlDocument contactDocument = new HtmlDocument();
            contactDocument.LoadHtml(match.Value);
            result.Name = contactDocument.DocumentNode.ChildNodes[1].InnerText;

            return result;
        }

        private Int64 ParseId(HtmlDocument htmlDocument)
        {
            return Int64.Parse(htmlDocument.GetElementbyId("idComplain").InnerText);
        }

        private Currency ParsePrice(HtmlDocument htmlDocument)
        {
            string priceText = htmlDocument.DocumentNode.SelectSingleNode("//*[@class='ad-price']/b").InnerHtml;
            Match match = Regex.Match(priceText, @"(\d+ )+");
            Currency result = new Currency
                                  {
                                      Amount = int.Parse(match.Value.Replace(" ", "")), 
                                      Name = Regex.Match(priceText, "[а-я.$]+").Value
                                  };

            return result;
        }
        #endregion

        #region Methods (protected)
        protected override bool DoesNextPageExist(HtmlDocument htmlDocument)
        {
            HtmlNode pagesNode = htmlDocument.DocumentNode.SelectNodes("descendant::div[@class]").First(div => div.Attributes.FirstOrDefault(attr => attr.Value.Contains("pages")) != null);

            Match match = Regex.Match(pagesNode.InnerHtml, @"(\d+) из (\d+)");
            string pageIndicator = match.Value;

            MatchCollection matchCollection = Regex.Matches(pageIndicator, RegExpPatterns._NUMBER);
            Debug.Assert(matchCollection.Count == 2);

            int currentPageNum = int.Parse(matchCollection[0].Value);
            int totalPageNum = int.Parse(matchCollection[1].Value);

            return currentPageNum < totalPageNum;
        }

        protected override HtmlDocument GetFirstPageDocument()
        {
            return HtmlDataGrabberTools.GetDocument(_START_QUERY_STRING);
        }

        protected override HtmlDocument GetNextPageDocument(HtmlDocument firstPage)
        {
            HtmlNode pagesNode = firstPage.DocumentNode.SelectNodes("descendant::div[@class]").First(div => div.Attributes.FirstOrDefault(attr => attr.Value.Contains("pages")) != null);
            HtmlNode nexPageNode = pagesNode.ChildNodes.Single(node => node.ChildNodes.Count(x => x.InnerHtml.Contains("Следующая")) > 0);
            return HtmlDataGrabberTools.GetDocument("http://fn.ua" + nexPageNode.Attributes["href"].Value);
        }

        protected override IEnumerable<AppartmentForSaleAd> GrabPage(HtmlDocument htmlDocument)
        {
            List<AppartmentForSaleAd> result = new List<AppartmentForSaleAd>();

            List<string> advertisementLinks = htmlDocument.DocumentNode.SelectNodes("descendant::td[@class='offer-desc']/h3/a").Select(x => x.Attributes["href"].Value).ToList();
            advertisementLinks.AddRange(htmlDocument.DocumentNode.SelectNodes("descendant::td[@class='hot-offer-desc']/h3/a").Select(x => x.Attributes["href"].Value));
            advertisementLinks = advertisementLinks.Where(x => Regex.Match(x, @"view.php\?ad_id=(\d+)").Success).ToList();
            foreach (string advertisementLink in advertisementLinks)
            {
                AppartmentForSaleAd appartmentForSaleAd = this.GrabAdvertisement(HtmlDataGrabberTools.GetDocument(advertisementLink));
                appartmentForSaleAd.AdvertisementUrl = advertisementLink;
                result.Add(appartmentForSaleAd);
            }

            return result;
        }
        #endregion

        #region Nested type: AppartmentForSaleAd
        public class AppartmentForSaleAd : IGrabbedEntity
        {
            #region Properties (public)
            public string OriginalText { get; set; }

            public Address Address { get; set; }

            public string AdvertisementUrl { get; set; }

            public DateTime? AnnouncementDate { get; set; }

            public AppartmentArea AppartmentArea { get; set; }

            public ContactInfo ContactInfo { get; set; }

            public Image[] Images { get; set; }

            public Int64 OriginalId { get; set; }

            public Currency Price { get; set; }

            public int RoomsCount { get; set; }

            public string Title { get; set; }

            public int ViewOfAll { get; set; }

            public int ViewsToday { get; set; }
            #endregion

            #region Properties (private)
            private string Metro { get; set; }
            #endregion
        }
        #endregion

        #region Nested type: PhoneItems
        public class PhoneItems
        {
            #region Properties (public)
            public string aphone1 { get; set; }

            public string aphone2 { get; set; }

            public string aphone3 { get; set; }
            #endregion
        }
        #endregion

        #region Nested type: Phones
        public class Phones
        {
            #region Properties (public)
            public PhoneItems items { get; set; }
            #endregion
        }
        #endregion
    }
}